using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace ASMdll
{
	internal class Command
	{
		private UInt16 address;
		private Byte[] code;
		private int numOp;
		private Operand op1;
		private Operand op2;
		private string cmdName;
		private string memoryName;
		private SymTabElement.SymType memoryType;
		private bool needWriteMemory;
		private int byteIndToWriteMemory;
		private bool needWriteD8;
		private int byteToWriteD8;
		private bool needWriteD16;
		private int byteToWriteD16;

		public Command(string cmd, UInt16 address)
		{
			this.address = address;

			Regex cmdZeroOperands = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s*$", RegexOptions.IgnoreCase);
			Regex cmdOneOperand = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+(?<op1>[_A-Z0-9\[\]+][_A-Z0-9\[\]\+\s]*)$", RegexOptions.IgnoreCase);
			Regex cmdTwoOperands = new Regex(@"^\s*(?<name>[_A-Z][_A-Z0-9]*)\s+(?<op1>[_A-Z0-9\[\]\+][_A-Z0-9\[\]\+\s]*),\s*(?<op2>[_A-Z0-9\[\]\+][_A-Z0-9\[\]\+\s]*)$", RegexOptions.IgnoreCase);

			#region ZERO OPERANDS

			Match m = cmdZeroOperands.Match(cmd);
			if (m.Success)
			{
				numOp = 0;
				cmdName = m.Groups["name"].Value.ToUpper();
				if (cmdName == "NOP")
					code = ZeroOperandsTranslator.ParseNOP(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else if (cmdName == "PUSHF")
					code = ZeroOperandsTranslator.ParsePUSHF(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else if (cmdName == "POPF")
					code = ZeroOperandsTranslator.ParsePOPF(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else if (cmdName == "RET")
					code = ZeroOperandsTranslator.ParseRET(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else if (cmdName == "CBW")
					code = ZeroOperandsTranslator.ParseCBW(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else if (cmdName == "CWD")
					code = ZeroOperandsTranslator.ParseCWD(ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				else
					throw new Exception("Unknown command!");

				return;
			}

			#endregion

			#region ONE OPERAND

			m = cmdOneOperand.Match(cmd);
			if (m.Success)
			{
				numOp = 1;
				op1 = new Operand(m.Groups["op1"].Value);
				cmdName = m.Groups["name"].Value.ToUpper();
				if (cmdName == "LOOP")
				{
					code = OneOperandTranslator.ParseLOOP(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "LOOPE")
				{
					code = OneOperandTranslator.ParseLOOPE(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "INT")
				{
					code = OneOperandTranslator.ParseINT(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				}
				else if (cmdName == "PUSH")
				{
					code = OneOperandTranslator.ParsePUSH(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "POP")
				{
					code = OneOperandTranslator.ParsePOP(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "JE")
				{
					code = OneOperandTranslator.ParseJE(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "JG")
				{
					code = OneOperandTranslator.ParseJG(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "JL")
				{
					code = OneOperandTranslator.ParseJL(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "JNE")
				{
					code = OneOperandTranslator.ParseJNE(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "JMP")
				{
					code = OneOperandTranslator.ParseJMP(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Label;
				}
				else if (cmdName == "CALL")
				{
					code = OneOperandTranslator.ParseCALL(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Procedure;
				}
				else if (cmdName == "RET")
				{
					code = OneOperandTranslator.ParseRET(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
				}
				else if (cmdName == "NOT")
				{
					code = OneOperandTranslator.ParseNOT(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "INC")
				{
					code = OneOperandTranslator.ParseINC(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "DEC")
				{
					code = OneOperandTranslator.ParseDEC(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "IDIV")
				{
					code = OneOperandTranslator.ParseIDIV(op1, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
				}
				else
					throw new Exception("Unknown command!");

				return;
			}

			#endregion

			#region TWO OPERANDS

			m = cmdTwoOperands.Match(cmd);
			if (m.Success)
			{
				numOp = 2;
				op1 = new Operand(m.Groups["op1"].Value);
				op2 = new Operand(m.Groups["op2"].Value);
				cmdName = m.Groups["name"].Value.ToUpper();
				if (cmdName == "MOV")
				{
					code = TwoOperandsTranslator.ParseMOV(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "LEA")
				{
					code = TwoOperandsTranslator.ParseLEA(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "ADD")
				{
					code = TwoOperandsTranslator.ParseADD(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "SUB")
				{
					code = TwoOperandsTranslator.ParseSUB(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "AND")
				{
					code = TwoOperandsTranslator.ParseAND(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "OR")
				{
					code = TwoOperandsTranslator.ParseOR(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "XOR")
				{
					code = TwoOperandsTranslator.ParseXOR(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "CMP")
				{
					code = TwoOperandsTranslator.ParseCMP(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "XCHG")
				{
					code = TwoOperandsTranslator.ParseXCHG(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "TEST")
				{
					code = TwoOperandsTranslator.ParseTEST(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "SHL")
				{
					code = TwoOperandsTranslator.ParseSHL(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "SHR")
				{
					code = TwoOperandsTranslator.ParseSHR(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else if (cmdName == "IMUL")
				{
					code = TwoOperandsTranslator.ParseIMUL(op1, op2, ref needWriteMemory, ref byteIndToWriteMemory, ref needWriteD8, ref byteToWriteD8, ref needWriteD16, ref byteToWriteD16);
					op1.SymType_ = SymTabElement.SymType.Pointer;
					op2.SymType_ = SymTabElement.SymType.Pointer;
				}
				else
					throw new Exception("Unknown command!");

				return;
			}

			#endregion

			throw new Exception("Invalid command!");
		}

		public UInt16 Size
		{
			get { return (UInt16)code.Length; }
		}

		private void SetMemoryName()
		{
			if (numOp == 0)
				throw new Exception("Internal compiler error!");
			if (numOp == 1)
			{
				if(op1.VariableName == null)
					throw new Exception("Internal compiler error!");
				memoryName = op1.VariableName;
				memoryType = op1.SymType_;
				return;
			}
			if (numOp == 2)
			{
				if (op1.VariableName == null && op2.VariableName == null)
					throw new Exception("Internal compiler error!");
				if (op1.VariableName != null && op2.VariableName != null)
					throw new Exception("Internal compiler error!");
				if (op1.VariableName != null)
				{
					memoryName = op1.VariableName;
					memoryType = op1.SymType_;
				}
				if (op2.VariableName != null)
				{
					memoryName = op2.VariableName;
					memoryType = op2.SymType_;
				}
				return;
			}
			throw new Exception("Internal compiler error!");
		}

		public void SetMemoryAddress(TranslationResult res)
		{
			if (needWriteMemory)
			{
				SetMemoryName();
				if(!res.SymTabContains(memoryName, memoryType))
					throw new Exception("There is no such identifier with name " + memoryName + "!");
				UInt16 adr = res.SymTabGetAddress(memoryName, memoryType);
				code[byteIndToWriteMemory] = (Byte)(adr & 255);
				code[byteIndToWriteMemory + 1] = (Byte)(adr >> 8);
			}
			if (needWriteD8)
			{
				SetMemoryName();
				if (!res.SymTabContains(memoryName, memoryType))
					throw new Exception("There is no such identifier with name " + memoryName + "!");
				int adr = res.SymTabGetAddress(memoryName, memoryType);
				adr = adr - address - code.Length;
				if (adr < -128 || adr > 127)
					throw new Exception("Too big distance between commands!");
				code[byteToWriteD8] = (Byte)(adr & 255);
			}
			if (needWriteD16)
			{
				SetMemoryName();
				if (!res.SymTabContains(memoryName, memoryType))
					throw new Exception("There is no such identifier with name " + memoryName + "!");
				int adr = res.SymTabGetAddress(memoryName, memoryType);
				adr = adr - address - code.Length;
				if (adr < -32768 || adr > 32767)
					throw new Exception("Too big distance between commands!");
				code[byteToWriteD16] = (Byte)(adr & 255);
				code[byteToWriteD16 + 1] = (Byte)(adr >> 8);
			}
		}

		public UInt16 Address
		{
			get { return address; }
		}

		public Byte GetByte(int ind)
		{
			return code[ind];
		}
	}
}
